#include "ParallelFwd2.h"

bool ParallelFwd2::prefix(char *s1, char *s2) {
	for ( ; *s1==*s2; ++s1, ++s2) {
		if (*(s2+1)=='\0') return true;
	}
	return false;
}

bool ParallelFwd2::substr(char *s1, char *s2) {
	int size1 = 0;
	int size2 = 0;
	while (s1[size1]!='\0') size1++;
	while (s2[size2]!='\0') size2++;
	if (size1==size2) return !strcmp(s1, s2);
	if (size1<size2) return false;
	for (int i=0; i<size1-size2+1; i++) {
		bool failed = false;
		for (int j=0; j<size2; j++) {
			if (s1[i+j-1]!=s2[j]) {
				failed = true;
				break;
			}
		}
		if (! failed) return true;
	}
	return false;
}

void ParallelFwd2::evalConstraint(int constraintIndex, int attrId) {
  //if (input.values[attrId].type!=input.constraints[attrId][constraintIndex].value.type) return;
  int *filterIdx;
  unsigned char numFilterIdx;
  if (input.values[attrId].type==INT) {
    IntOMPConstraint2 *constr = (IntOMPConstraint2 *) input.constraints[attrId];
    numFilterIdx = constr[constraintIndex].numFilterIdx;
    filterIdx = constr[constraintIndex].filter;
    if ((constr[constraintIndex].op==EQ && input.values[attrId].intVal!=constr[constraintIndex].value) ||
	(constr[constraintIndex].op==LT && input.values[attrId].intVal>=constr[constraintIndex].value) ||
	(constr[constraintIndex].op==GT && input.values[attrId].intVal<=constr[constraintIndex].value) ||
	(constr[constraintIndex].op==DF && input.values[attrId].intVal==constr[constraintIndex].value))
      return;
  } else {
    StringOMPConstraint2 *constr = (StringOMPConstraint2 *) input.constraints[attrId];
    numFilterIdx = constr[constraintIndex].numFilterIdx;
    filterIdx = constr[constraintIndex].filter;
    if ((constr[constraintIndex].op==EQ &&  strcmp(input.values[attrId].stringVal, constr[constraintIndex].value)) ||
	(constr[constraintIndex].op==DF && !strcmp(input.values[attrId].stringVal, constr[constraintIndex].value)) ||
	(constr[constraintIndex].op==PF && !prefix(input.values[attrId].stringVal, constr[constraintIndex].value)) ||
	(constr[constraintIndex].op==IN && !substr(input.values[attrId].stringVal, constr[constraintIndex].value)))
      return;
  }
  int idx;
  for(int i=0; i<numFilterIdx; i++) {
    idx=filterIdx[i];
    //#pragma omp atomic
    filtersCount[idx]++;
    if (filtersCount[idx]>=filtersInfos[idx].numConstraints) {
      interfaces[filtersInfos[idx].interface] = true;
    }
  }
}

ParallelFwd2::ParallelFwd2() {
	numInterfaces = 0;
	numFilters = 0;
	consolidated = false;
	inputTime = 0;
	execTime = 0;
	resultsTime = 0;
	//omp_set_num_threads(6);
}

ParallelFwd2::~ParallelFwd2() {
	for (map<int, set<OMPFilter *> >::iterator it=hostFilters.begin(); it!=hostFilters.end(); ++it) {
		for (set<OMPFilter *>::iterator it2=it->second.begin(); it2!=it->second.end(); ++it2) {
			OMPFilter *cf = *it2;
			delete cf;
		}
	}
	if (! consolidated) return;
	for (map<string_t, void *>::iterator it=nameConstraints.begin(); it!=nameConstraints.end(); ++it) {
	  if(nameType[it->first]==INT) {
		IntOMPConstraint2 *constr = (IntOMPConstraint2 *) it->second;
		free(constr);
	  } else {
	    StringOMPConstraint2 *constr = (StringOMPConstraint2 *) it->second;
		free(constr);
	  }
	}
	free(filtersInfos);
	free(filtersCount);
	free(interfaces);
}

void ParallelFwd2::ifConfig(int interfaceId, set<OMPFilter *> &filters) {
  // record the set of filters associated to this interface
  hostFilters.insert(make_pair(interfaceId, filters));

  // update the constraintsSize and nameType data structures (to be used at consolidate time)
  for (set<OMPFilter *>::iterator it=filters.begin(); it!=filters.end(); ++it) {
    OMPFilter *filter = *it;
    for (int i=0; i<filter->numConstraints; i++) {
      string_t name = filter->constraints[i].name;
      map<string_t, int>::iterator it=numConstraints.find(name);
      if (it==numConstraints.end()) {
	numConstraints.insert(make_pair(name, 1));
      } else {
	it->second++;
      }
      map<string_t, OMPType>::iterator it1=nameType.find(name);
      if (it1==nameType.end()) {
        nameType.insert(make_pair(name, filter->constraints[i].value.type));
      }
    }
    numFilters++;
  }
  numInterfaces++;
}

void ParallelFwd2::consolidate() {
	// Creates and sets the interfaces data structure
	interfaces = (bool*) malloc(sizeof(bool)*numInterfaces);
	memset(interfaces, 0, sizeof(bool)*numInterfaces);
	// Creates and sets the filtersCount data structure
	filtersCount = (unsigned short *) malloc(sizeof(unsigned short)*numFilters);
#pragma omp parallel for
	for (int i=0; i<numFilters; i++) {
		filtersCount[i] = 0x00u;
	}
	map<string_t, int> currentSize;
	for (map<string_t, int>::iterator it=numConstraints.begin(); it!=numConstraints.end(); ++it) {
		string_t name = it->first;
		int size = it->second;
		void *arrayVal;
		if(nameType[name]==INT) {
		  arrayVal = malloc(sizeof(IntOMPConstraint2)*size);
		} else {
		  arrayVal = malloc(sizeof(StringOMPConstraint2)*size);
		}
		nameConstraints.insert(make_pair(name, arrayVal));
		currentSize.insert(make_pair(name, 0));
	}
	// Creates and sets the filtersConstraints and filtersInterface data structures
	filtersInfos = (OMPFilterInfo*) malloc(sizeof(OMPFilterInfo)*numFilters);
	int filterId = 0;
	for (map<int, set<OMPFilter *> >::iterator it=hostFilters.begin(); it!=hostFilters.end(); ++it) {
		for (set<OMPFilter *>::iterator it2=it->second.begin(); it2!=it->second.end(); ++it2) {
			OMPFilter * filter = *it2;
			filtersInfos[filterId].interface = it->first;
			filtersInfos[filterId].numConstraints = filter->numConstraints;
			for (int i=0; i<filter->numConstraints; i++) {
				string_t name = filter->constraints[i].name;
				map<string_t, int>::iterator currentIt = currentSize.find(name);
				if(nameType[name]==INT) {
				  IntOMPConstraint2 *original = findIntConstraint((IntOMPConstraint2 *)nameConstraints[name], currentIt->second, filter->constraints[i]);
				  if(original!=NULL && original->numFilterIdx<MAX_FILTERS_X_CONSTR) {
				    original->filter[original->numFilterIdx++]=filterId;
				  } else {				    
				    buildIntConstraint(((IntOMPConstraint2 *)nameConstraints[name])+currentIt->second, filter->constraints[i], filterId);
				    currentIt->second++;
				  }
				} else {
				  StringOMPConstraint2 *original = findStringConstraint((StringOMPConstraint2 *)nameConstraints[name], currentIt->second, filter->constraints[i]);
				  if(original!=NULL && original->numFilterIdx<MAX_FILTERS_X_CONSTR) {
				    original->filter[original->numFilterIdx++]=filterId;
				  } else {				    
				    buildStringConstraint(((StringOMPConstraint2 *)nameConstraints[name])+currentIt->second, filter->constraints[i], filterId);
				    currentIt->second++;
				  }
				}
			}
			filterId++;
		}
	}
	// update the number of constraints per name (considering duplicates were removed)
	for(map<string_t,int>::iterator it=currentSize.begin(); it!=currentSize.end(); ++it) {
	  numConstraints[it->first] = it->second;
	}
	consolidated = true;
	int totConstr=0;
	for(map<string_t,int>::iterator it=numConstraints.begin(); it!=numConstraints.end(); ++it) {
	  totConstr+=it->second;
	}
	cout << "TOTAL NUMBER OF CONSTRAINTS: " << totConstr << endl;
}

void ParallelFwd2::getStats(double *in, double *exec, double *res) {
	*in = inputTime;
	*exec = execTime;
	*res = resultsTime;
}

#if STATS==1
void ParallelFwd2::processMessage(OMPOutbox *outbox) {
	Timer t;
	t.start();
	int maxConstr = makeInput(outbox->message);
	inputTime += t.stop();
	if (maxConstr>0) {
		t.start();
		computeResults(maxConstr);
		execTime += t.stop();
		t.start();
		getMatchingInterfaces(outbox->outgoingInterfaces);
		resultsTime += t.stop();
	}
}
#elif STATS==0
void ParallelFwd2::processMessage(OMPOutbox *outbox) {
	int maxConstr = makeInput(outbox->message);
	if (maxConstr>0) {
		computeResults(maxConstr);
		getMatchingInterfaces(outbox->outgoingInterfaces);
	}
}
#endif

int ParallelFwd2::makeInput(OMPMessage *message) {
	int dest = 0;
	int maxConstr = 0;
	for (int i=0; i<message->numAttributes; i++) {
		string_t name = message->attributes[i].name;
		map<string_t, int>::iterator it = numConstraints.find(name);
		if (it==numConstraints.end()) continue;
		input.constraintsSize[dest] = it->second;
		input.constraints[dest] = nameConstraints.find(name)->second;
		if (input.constraintsSize[dest]>maxConstr) maxConstr = input.constraintsSize[dest];
		input.values[dest] = message->attributes[i].value;
		dest++;
	}
	input.constraintsNum = dest;
	return maxConstr;
}

void ParallelFwd2::computeResults(int maxConstr) {
	for (int j=0; j<input.constraintsNum; j++) {
#pragma omp parallel for
		for (int i=0; i<input.constraintsSize[j]; i++) {
			evalConstraint(i, j);
		}
	}
}

void ParallelFwd2::getMatchingInterfaces(set<int> &results) {
#pragma omp parallel for
	for (int i=0; i<numInterfaces; i++) {
		if (interfaces[i]==true) {
#pragma omp critical
			results.insert(i);
			interfaces[i] = false;
		}
	}
#pragma omp parallel for
	for(int i=0; i<numFilters; i++) {
		filtersCount[i] = 0x00u;
	}
}

IntOMPConstraint2 *ParallelFwd2::findIntConstraint(IntOMPConstraint2 *constraints, int numConstraints, OMPConstraint &constraint) {
  for(int i=0; i<numConstraints; i++) {
    if(constraints[i].op == constraint.op && constraints[i].value == constraint.value.intVal &&  constraints[i].numFilterIdx<MAX_FILTERS_X_CONSTR) {
      return constraints+i;
    }
  }
  return NULL;
}

StringOMPConstraint2 *ParallelFwd2::findStringConstraint(StringOMPConstraint2 *constraints, int numConstraints, OMPConstraint &constraint) {
  for(int i=0; i<numConstraints; i++) {
    if(constraints[i].op == constraint.op && strncmp(constraints[i].value,constraint.value.stringVal,STRING_VAL_LEN)==0 &&  constraints[i].numFilterIdx<MAX_FILTERS_X_CONSTR) {
      return constraints+i;
    }
  }
  return NULL;
}

void ParallelFwd2::buildIntConstraint(IntOMPConstraint2 *result, OMPConstraint &constraint, int filterId) {
	result->op = constraint.op;
	result->value = constraint.value.intVal;
	result->filter[0] = filterId;
	result->numFilterIdx = 1;
}

void ParallelFwd2::buildStringConstraint(StringOMPConstraint2 *result, OMPConstraint &constraint, int filterId) {
	result->op = constraint.op;
	memcpy(result->value, constraint.value.stringVal,STRING_VAL_LEN);
	result->filter[0] = filterId;
	result->numFilterIdx = 1;
}
